package org.andengine.entity.util;

import org.andengine.BuildConfig;
import org.andengine.engine.handler.IUpdateHandler;
import org.andengine.util.TextUtils;
import org.andengine.util.debug.Debug;
import org.andengine.util.debug.Debug.DebugLevel;
import org.andengine.util.system.SystemUtils;
import org.andengine.util.system.SystemUtils.SystemUtilsException;


/**
 * (c) 2012 Zynga Inc.
 *
 * @author Nicolas Gramlich <ngramlich@zynga.com>
 * @since 14:50:15 - 14.05.2012
 */
public class MemoryLogger implements IUpdateHandler {
    // ===========================================================
    // Constants
    // ===========================================================

    private static final float AVERAGE_DURATION_DEFAULT = 5;

    // ===========================================================
    // Fields
    // ===========================================================

    private final float mAverageDuration;
    private final DebugLevel mDebugLevel;

    private float mSecondsElapsed;

    private boolean mLogSystemMemory;
    private long mPreviousSystemMemorySize;
    private long mPreviousSystemMemoryFreeSize;

    private boolean mLogDalvikHeap;
    private long mPreviousDalvikHeapSize;
    private long mPreviousDalvikHeapFreeSize;
    private long mPreviousDalvikHeapAllocatedSize;

    private boolean mLogDalvikMemoryInfo;
    private long mPreviousDalvikProportionalSetSize;
    private long mPreviousDalvikPrivateDirtyPages;
    private long mPreviousDalvikSharedDirtyPages;

    private boolean mLogNativeHeap;
    private long mPreviousNativeHeapSize;
    private long mPreviousNativeHeapFreeSize;
    private long mPreviousNativeHeapAllocatedSize;

    private boolean mLogNativeMemoryInfo;
    private long mPreviousNativeProportionalSetSize;
    private long mPreviousNativePrivateDirtyPages;
    private long mPreviousNativeSharedDirtyPages;

    // ===========================================================
    // Constructors
    // ===========================================================

    public MemoryLogger() {
        this(DebugLevel.DEBUG);
    }

    public MemoryLogger(final DebugLevel pDebugLevel) {
        this(MemoryLogger.AVERAGE_DURATION_DEFAULT, pDebugLevel);
    }

    public MemoryLogger(final float pAverageDuration) {
        this(pAverageDuration, DebugLevel.DEBUG);
    }

    public MemoryLogger(final float pAverageDuration, final DebugLevel pDebugLevel) {
        this(pAverageDuration, pDebugLevel, true, true, false, true, false);
    }

    public MemoryLogger(final float pAverageDuration, final DebugLevel pDebugLevel, final boolean pLogSystemMemory, final boolean pLogDalvikHeap, final boolean pLogDalvikMemoryInfo, final boolean pLogNativeHeap, final boolean pLogNativeMemoryInfo) {
        this.mAverageDuration = pAverageDuration;
        this.mDebugLevel = pDebugLevel;

        this.mLogSystemMemory = pLogSystemMemory;
        this.mLogDalvikHeap = pLogDalvikHeap;
        this.mLogDalvikMemoryInfo = pLogDalvikMemoryInfo;
        this.mLogNativeHeap = pLogNativeHeap;
        this.mLogNativeMemoryInfo = pLogNativeMemoryInfo;
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    public static final CharSequence formatRight(final long pLong, final char pPadChar, final int pLength) {
        return MemoryLogger.formatRight(pLong, pPadChar, pLength, false);
    }

    public static final CharSequence formatRight(final long pLong, final char pPadChar, final int pLength, final boolean pAddPositiveSign) {
        if ((pLong > 0) && pAddPositiveSign) {
            return TextUtils.padFront("+" + String.valueOf(pLong), pPadChar, pLength);
        } else {
            return TextUtils.padFront(String.valueOf(pLong), pPadChar, pLength);
        }
    }

    // ===========================================================
    // Methods
    // ===========================================================

    @Override
    public void onUpdate(final float pSecondsElapsed) {
        this.mSecondsElapsed += pSecondsElapsed;

        if (this.mSecondsElapsed > this.mAverageDuration) {
            this.onHandleLogDurationElapsed();

            this.mSecondsElapsed -= this.mAverageDuration;
        }
    }

    @Override
    public void reset() {
        this.mSecondsElapsed = 0;
    }

    protected void onHandleLogDurationElapsed() {
        if (BuildConfig.DEBUG) {
            /* Execute GC. */
            System.gc();
            try {
                final StringBuilder stringBuilder = new StringBuilder();
                stringBuilder.append("+------------------------------+---------------+-----------------+\n");
                stringBuilder.append("|         Memory Stat          |    Current    |      Change     |\n");
                stringBuilder.append("+------------------------------+---------------+-----------------+\n");

                if (this.mLogSystemMemory) {
                    final long systemMemorySize = SystemUtils.getSystemMemorySize();
                    final long systemMemoryFreeSize = SystemUtils.getSystemMemoryFreeSize();

                    final long systemMemorySizeDiff = systemMemorySize - this.mPreviousSystemMemorySize;
                    final long systemMemoryFreeSizeDiff = systemMemoryFreeSize - this.mPreviousSystemMemoryFreeSize;

                    this.mPreviousSystemMemorySize = systemMemorySize;
                    this.mPreviousSystemMemoryFreeSize = systemMemoryFreeSize;

                    stringBuilder.append("| System memory size           | " + MemoryLogger.formatRight(systemMemorySize, ' ', 10) + " kB | (" + MemoryLogger.formatRight(systemMemorySizeDiff, ' ', 10, true) + " kB) |\n");
                    stringBuilder.append("| System memory free size      | " + MemoryLogger.formatRight(systemMemoryFreeSize, ' ', 10) + " kB | (" + MemoryLogger.formatRight(systemMemoryFreeSizeDiff, ' ', 10, true) + " kB) |\n");
                    stringBuilder.append("+------------------------------+---------------+-----------------+\n");
                }

                if (this.mLogDalvikHeap) {
                    final long dalvikHeapSize = SystemUtils.getDalvikHeapSize();
                    final long dalvikHeapFreeSize = SystemUtils.getDalvikHeapFreeSize();
                    final long dalvikHeapAllocatedSize = SystemUtils.getDalvikHeapAllocatedSize();

                    final long dalvikHeapSizeDiff = dalvikHeapSize - this.mPreviousDalvikHeapSize;
                    final long dalvikHeapFreeSizeDiff = dalvikHeapFreeSize - this.mPreviousDalvikHeapFreeSize;
                    final long dalvikHeapAllocatedSizeDiff = dalvikHeapAllocatedSize - this.mPreviousDalvikHeapAllocatedSize;

                    stringBuilder.append("| Dalvik memory size           | " + MemoryLogger.formatRight(dalvikHeapSize, ' ', 10) + " kB | (" + MemoryLogger.formatRight(dalvikHeapSizeDiff, ' ', 10, true) + " kB) |\n");
                    stringBuilder.append("| Dalvik memory free size      | " + MemoryLogger.formatRight(dalvikHeapFreeSize, ' ', 10) + " kB | (" + MemoryLogger.formatRight(dalvikHeapFreeSizeDiff, ' ', 10, true) + " kB) |\n");
                    stringBuilder.append("| Dalvik memory allocated size | " + MemoryLogger.formatRight(dalvikHeapAllocatedSize, ' ', 10) + " kB | (" + MemoryLogger.formatRight(dalvikHeapAllocatedSizeDiff, ' ', 10, true) + " kB) |\n");
                    stringBuilder.append("+------------------------------+---------------+-----------------+\n");

                    this.mPreviousDalvikHeapSize = dalvikHeapSize;
                    this.mPreviousDalvikHeapFreeSize = dalvikHeapFreeSize;
                    this.mPreviousDalvikHeapAllocatedSize = dalvikHeapAllocatedSize;
                }

                if (this.mLogDalvikMemoryInfo) {
                    final long dalvikProportionalSetSize = SystemUtils.getDalvikProportionalSetSize();
                    final long dalvikPrivateDirtyPages = SystemUtils.getDalvikPrivateDirtyPages();
                    final long dalvikSharedDirtyPages = SystemUtils.getDalvikSharedDirtyPages();

                    final long dalvikProportionalSetSizeDiff = dalvikProportionalSetSize - this.mPreviousDalvikProportionalSetSize;
                    final long dalvikPrivateDirtyPagesDiff = dalvikPrivateDirtyPages - this.mPreviousDalvikPrivateDirtyPages;
                    final long dalvikSharedDirtyPagesDiff = dalvikSharedDirtyPages - this.mPreviousDalvikSharedDirtyPages;

                    stringBuilder.append("| Dalvik proportional set size | " + MemoryLogger.formatRight(dalvikProportionalSetSize, ' ', 10) + "    | (" + MemoryLogger.formatRight(dalvikProportionalSetSizeDiff, ' ', 10, true) + "   ) |\n");
                    stringBuilder.append("| Dalvik private dirty pages   | " + MemoryLogger.formatRight(dalvikPrivateDirtyPages, ' ', 10) + "    | (" + MemoryLogger.formatRight(dalvikPrivateDirtyPagesDiff, ' ', 10, true) + "   ) |\n");
                    stringBuilder.append("| Dalvik shared dirty pages    | " + MemoryLogger.formatRight(dalvikSharedDirtyPages, ' ', 10) + "    | (" + MemoryLogger.formatRight(dalvikSharedDirtyPagesDiff, ' ', 10, true) + "   ) |\n");
                    stringBuilder.append("+------------------------------+---------------+-----------------+\n");

                    this.mPreviousDalvikProportionalSetSize = dalvikProportionalSetSize;
                    this.mPreviousDalvikPrivateDirtyPages = dalvikPrivateDirtyPages;
                    this.mPreviousDalvikSharedDirtyPages = dalvikSharedDirtyPages;
                }

                if (this.mLogNativeHeap) {
                    final long nativeHeapSize = SystemUtils.getNativeHeapSize();
                    final long nativeHeapFreeSize = SystemUtils.getNativeHeapFreeSize();
                    final long nativeHeapAllocatedSize = SystemUtils.getNativeHeapAllocatedSize();

                    final long nativeHeapSizeDiff = nativeHeapSize - this.mPreviousNativeHeapSize;
                    final long nativeHeapFreeSizeDiff = nativeHeapFreeSize - this.mPreviousNativeHeapFreeSize;
                    final long nativeHeapAllocatedSizeDiff = nativeHeapAllocatedSize - this.mPreviousNativeHeapAllocatedSize;

                    stringBuilder.append("| Native memory size           | " + MemoryLogger.formatRight(nativeHeapSize, ' ', 10) + " kB | (" + MemoryLogger.formatRight(nativeHeapSizeDiff, ' ', 10, true) + " kB) |\n");
                    stringBuilder.append("| Native memory free size      | " + MemoryLogger.formatRight(nativeHeapFreeSize, ' ', 10) + " kB | (" + MemoryLogger.formatRight(nativeHeapFreeSizeDiff, ' ', 10, true) + " kB) |\n");
                    stringBuilder.append("| Native memory allocated size | " + MemoryLogger.formatRight(nativeHeapAllocatedSize, ' ', 10) + " kB | (" + MemoryLogger.formatRight(nativeHeapAllocatedSizeDiff, ' ', 10, true) + " kB) |\n");
                    stringBuilder.append("+------------------------------+---------------+-----------------+\n");

                    this.mPreviousNativeHeapSize = nativeHeapSize;
                    this.mPreviousNativeHeapFreeSize = nativeHeapFreeSize;
                    this.mPreviousNativeHeapAllocatedSize = nativeHeapAllocatedSize;
                }

                if (this.mLogNativeMemoryInfo) {
                    final long nativeProportionalSetSize = SystemUtils.getNativeProportionalSetSize();
                    final long nativePrivateDirtyPages = SystemUtils.getNativePrivateDirtyPages();
                    final long nativeSharedDirtyPages = SystemUtils.getNativeSharedDirtyPages();

                    final long nativeProportionalSetSizeDiff = nativeProportionalSetSize - this.mPreviousNativeProportionalSetSize;
                    final long nativePrivateDirtyPagesDiff = nativePrivateDirtyPages - this.mPreviousNativePrivateDirtyPages;
                    final long nativeSharedDirtyPagesDiff = nativeSharedDirtyPages - this.mPreviousNativeSharedDirtyPages;

                    stringBuilder.append("| Native proportional set size | " + MemoryLogger.formatRight(nativeProportionalSetSize, ' ', 10) + "    | (" + MemoryLogger.formatRight(nativeProportionalSetSizeDiff, ' ', 10, true) + "   ) |\n");
                    stringBuilder.append("| Native private dirty pages   | " + MemoryLogger.formatRight(nativePrivateDirtyPages, ' ', 10) + "    | (" + MemoryLogger.formatRight(nativePrivateDirtyPagesDiff, ' ', 10, true) + "   ) |\n");
                    stringBuilder.append("| Native shared dirty pages    | " + MemoryLogger.formatRight(nativeSharedDirtyPages, ' ', 10) + "    | (" + MemoryLogger.formatRight(nativeSharedDirtyPagesDiff, ' ', 10, true) + "   ) |\n");
                    stringBuilder.append("+------------------------------+---------------+-----------------+\n");

                    this.mPreviousNativeProportionalSetSize = nativeProportionalSetSize;
                    this.mPreviousNativePrivateDirtyPages = nativePrivateDirtyPages;
                    this.mPreviousNativeSharedDirtyPages = nativeSharedDirtyPages;
                }

                Debug.log(this.mDebugLevel, stringBuilder.toString());
            } catch (final SystemUtilsException e) {
                Debug.e(e);
            }
            System.gc();
        }
    }

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================
}
